package demo4

import (
	"fmt"
)

/*
	类型断言
	1.Go语言中有四种接口相关的类型转换情形：
		1. 将一个非接口值转换为一个接口类型。在这样的转换中，此非接口值的类型必须实现了此接口类型。
		2. 将一个接口值转换为另一个接口类型（前者接口值的类型实现了后者目标接口类型）。
		3. 将一个接口值转换为一个非接口类型（此非接口类型必须实现了此接口值的接口类型）。
		4. 将一个接口值转换为另一个接口类型（前者接口值的类型可以实现了也可以未实现后者目标接口类型）。

	2.	本节将介绍后面两种情形。这两种情形的合法性是在运行时刻通过类型断言来验证的。
		事实上，类型断言同样也适用于上面列出的第二种情形。

	3.	一个类型断言表达式的语法为 i.(T)，其中 i 为一个接口值， T 为一个类型名或者类型字面表示。
		类型 T 可以为任意一个非接口类型，或者一个任意接口类型。

		在一个类型断言表达式 i.(T) 中， i 称为断言值， T 称为断言类型。 一个断言可能成功或者失败。

	4.	按照上述规则，如果一个类型断言中的断言值是一个零值 nil 接口值，则此断言必定失败。

	5. 对于大多数场合，一个类型断言被用做一个单值表达式。
		但是，当一个类型断言被用做一个赋值语句中的唯一源值时，此断言可以返回一个可选的第二个结果并被视作为一个多值表达式。
		此可选的第二个结果为一个类型不确定的布尔值，用来表示此断言是否成功了。

	6. 如果一个断言失败并且它的可选的第二个结果未呈现，则此断言将造成一个恐慌。
*/

/* demo2 */
type writer2 interface {
	write(buf []byte) (int, error)
}

type dummyWriter struct{}

func (dummyWriter) write(buf []byte) (int, error) {
	return len(buf), nil
}

// TestDemo45 类型断言
func TestDemo45() {
	/* demo1: 断言类型为非接口类型： */
	// 编译器将把123的类型推断为内置类型int。
	var x interface{} = 123

	// 情形一
	n, ok := x.(int)
	fmt.Println(n, ok) // 123, true
	n = x.(int)
	fmt.Println(n) // 123

	// 情形二
	a, ok := x.(float64)
	fmt.Println(a, ok) // 0 false

	// 情形三
	// a = x.(float64) //将产生一个恐慌
	// fmt.Println(a)

	/* demo2: 断言类型为接口类型 */
	var test2 interface{} = dummyWriter{}
	// y 的动态类型为内置类型 string
	var y interface{} = "abc"
	var w writer2
	var ok2 bool

	// DummyWriter既实现了Writer，也实现了interface{}。
	w, ok2 = test2.(writer2)
	fmt.Println(w, ok2) // {}  true
	test2, ok2 = w.(interface{})
	fmt.Println(test2, ok2) // {} true

	// y的动态类型为string。string类型并没有实现Writer。
	w, ok2 = y.(writer2)
	fmt.Println(w, ok2) // <nil> false
	// w = y.(writer2) // 将产生一个恐慌

	// tips: 事实上，对于一个动态类型为 T 的接口值 i，方法调用 i.m(...) 等价于 i.(T).m(...)。
}
